package prvydevw.servlet;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import prvydevw.entity.Payment;
import prvydevw.entity.Subject;
import prvydevw.manager.PaymentManager;
import prvydevw.manager.ServiceFailureException;
import prvydevw.manager.SubjectManager;

/**
 *
 * @author ivo
 */
public class PaymentsServlet extends HttpServlet
{
    private static final String LIST_JSP = "/payments.jsp";
    private static final String EXPORT_SERVLET = "/export";
    
    private Stack<Notification> notifications = new Stack<Notification>();
    
    private Subject filterSubject;
    private Calendar filterDateFrom, filterDateTo;
    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        String action = request.getPathInfo();
        List<Notification> notifs = new ArrayList<Notification>();
        
        while(!notifications.empty()) {
            notifs.add(notifications.pop());
        }
        
        request.setCharacterEncoding("utf-8");
        request.setAttribute("notifications", notifs);
        
        if (action.equals("/list")) {
            listAction(request, response);
        }
        else if (action.equals("/save")) {
            saveAction(request, response);
        }
        else if (action.equals("/delete")) {
            deleteAction(request, response);
        }
        else if (action.equals("/filter")) {
            filterAction(request, response);
        }
        else if (action.equals("/export")) {
            request.setAttribute("exportType", "docbook");
            exportAction(request, response);
        }
        else if (action.equals("/exportpdf")) {
            request.setAttribute("exportType", "pdf");
            exportAction(request, response);
        }
        else {
            response.sendError(404, "Page not found");
        }
    }
    
    private void listAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        prepareListData( request);
        
        request.getRequestDispatcher(LIST_JSP).forward(request, response);
    }
    
    private void saveAction(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        Payment payment = getPaymentFromRequest(request);
        
        if (payment == null) {
            manageRedirect(request, response, payment);
            
            return;
        }
        
        PaymentManager manager = (PaymentManager) getServletContext().getAttribute("paymentManager");
        
        try {
            if (payment.getId() == null) {
                manager.createPayment(payment);
                
                if (filterSubject != null || filterDateFrom != null || filterDateTo != null) {
                    notifications.add(new Notification("info", "Máte aktívny filter. Je možné, že sa pridaná platba nezobrazí."));
                }
            }
            else {
                manager.updatePayment(payment);
            }
            
            notifications.add(new Notification("success", "Platba byla úspěšně uložena"));
        }
        catch (ServiceFailureException e) {
            notifications.add(new Notification("error", "Vyskytla sa chyba pri ukladaní platby"));
            
            Logger.getLogger(PaymentsServlet.class.getName()).log(Level.SEVERE, "Error saving payment", e);
        }
        
        manageRedirect(request, response, payment);
    }
    
    private void deleteAction(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        Long id;
        String paymentId = request.getParameter("payment");
        
        if (paymentId == null || paymentId.isEmpty()) {
            notifications.add(new Notification("error", "Musí být zadána platba pro odstranění"));
            
            manageRedirect(request, response, null);
            return;
        }
        
        try {
            id = Long.parseLong(paymentId);
        }
        catch (NumberFormatException e) {
            notifications.add(new Notification("error", "Neplatná platba"));
            
            manageRedirect(request, response, null);
            return;
        }
        
        PaymentManager manager = (PaymentManager) getServletContext().getAttribute("paymentManager");
        Payment payment = manager.findPayment(id);
        
        if (payment == null) {
            notifications.add(new Notification("error", "Platba nebyla nalezena"));
            
            manageRedirect(request, response, null);
            return;
        }
        
        try {
            manager.deletePayment(payment);
            
            notifications.add(new Notification("success", "Platba byla úspěšně smazána"));
        }
        catch (ServiceFailureException e) {
            notifications.add(new Notification("error", "Vyskytla sa chyba při mazaní platby"));
            
            Logger.getLogger(PaymentsServlet.class.getName()).log(Level.SEVERE, "Error saving payment", e);
        }
        
        manageRedirect(request, response, payment);
    }
    
    private void filterAction(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        String subjectId = request.getParameter("subject");
        String dateFromString = request.getParameter("from");
        String dateToString = request.getParameter("to");
        
        SubjectManager subjectManager = (SubjectManager) getServletContext().getAttribute("subjectManager");
        
        if (subjectId != null && !subjectId.isEmpty()) {
            Long id = null;
            
            try {
                id = Long.parseLong(subjectId);
            }
            catch(NumberFormatException e) {
                notifications.push(new Notification("error", "Neplatný subjekt"));
            }
            
            filterSubject = (id != null) ? subjectManager.findSubject(id) : null;
        }
        else {
            filterSubject = null;
        }
        
        if (dateFromString != null && !dateFromString.isEmpty()) {
            Date dateFrom = null;
            Calendar dateFromCalendar = Calendar.getInstance();
            
            try {
                dateFrom = parseDate(dateFromString);
            }
            catch (ParseException e) {
                notifications.push(new Notification("error", "Neplatný dátum od"));
            }
            
            if (dateFrom != null) {
                dateFromCalendar.setTime(dateFrom);
                
                filterDateFrom = dateFromCalendar;
            }
            else {
                filterDateFrom = null;
            }
        }
        else {
            filterDateFrom = null;
        }
        
        if (dateToString != null && !dateToString.isEmpty()) {
            Date dateTo = null;
            Calendar dateToCalendar = Calendar.getInstance();
            
            try {
                dateTo = parseDate(dateToString);
            }
            catch (ParseException e) {
                notifications.push(new Notification("error", "Neplatný dátum do"));
            }
            
            if (dateTo != null) {
                dateToCalendar.setTime(dateTo);
                
                filterDateTo = dateToCalendar;
            }
            else {
                filterDateTo = null;
            }
        }
        else {
            filterDateTo = null;
        }
        
        response.sendRedirect("list");
    }
    
        private void exportAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        prepareListData( request);
        
        request.getRequestDispatcher(EXPORT_SERVLET).forward(request, response);
    }
    
    private BigDecimal getIncome(List<Payment> payments)
    {
        BigDecimal income = new BigDecimal("0.00");
        
        for (Payment payment : payments) {
            if (payment.getAmount().compareTo(BigDecimal.ZERO) > 0) {
                income = income.add(payment.getAmount());
            }
        }
        
        return income;
    }
    
    private BigDecimal getExpenses(List<Payment> payments)
    {
        BigDecimal expenses = new BigDecimal("0.00");
        
        for (Payment payment : payments) {
            if (payment.getAmount().compareTo(BigDecimal.ZERO) < 0) {
                expenses = expenses.add(payment.getAmount());
            }
        }
        
        return expenses;
    }
    
    private Payment getPaymentFromRequest(HttpServletRequest request)
    {   
        Map<String, String[]> requestParams = request.getParameterMap();
        String key = getPaymentKey(requestParams);
        Map<String, String> params = getPaymentParams(requestParams, key);
        
        if (params.get("date") == null || params.get("date").isEmpty()) {
            notifications.push(new Notification("error", "Datum platby musí být vyplněn"));
            
            return null;
        }
        
        Date date;
        
        try {
            date = parseDate(params.get("date"));
        }
        catch (ParseException ex) {
            notifications.push(new Notification("error", "Zadané datum není platné"));
            
            return null;
        }
        
        if (params.get("subject") == null || params.get("subject").isEmpty()) {
            notifications.push(new Notification("error", "Subjekt musí být vyplněn"));
            
            return null;
        }
        
        Long subjectId;
        
        try {
            subjectId = Long.parseLong(params.get("subject"));
        }
        catch (NumberFormatException e) {
            notifications.push(new Notification("error", "Subjekt musí být celé číslo"));
            
            return null;
        }
        
        SubjectManager manager = (SubjectManager) getServletContext().getAttribute("subjectManager");
        
        if (manager.findSubject(subjectId) == null) {
            notifications.push(new Notification("error", "Zadaný subjekt neexistuje"));
            
            return null;
        }
        
        if (params.get("amount") == null || params.get("amount").isEmpty()) {
            notifications.push(new Notification("error", "Částka musí být vyplněna"));
            
            return null;
        }
        
        if (params.get("number") == null || params.get("number").isEmpty()) {
            notifications.push(new Notification("error", "Číslo musí být vyplněno"));
            
            return null;
        }
        
        Long id = null;
        
        if (params.get("id") != null && !params.get("id").isEmpty()) {
            try {
                id = Long.parseLong(params.get("id"));
            }
            catch (NumberFormatException e) {
                notifications.push(new Notification("error", "ID musí být celé číslo"));
            
                return null;
            }
        }
        
        Payment payment = new Payment();
        
        BigDecimal amount = new BigDecimal(params.get("amount"));
        Calendar dateCalendar = Calendar.getInstance();
        dateCalendar.setTime(date);
        
        payment.setId(id);
        payment.setAmount(amount);
        payment.setDate(dateCalendar);
        payment.setDescription(params.get("description"));
        payment.setNumber(params.get("number"));
        payment.setSubjectID(subjectId);
        
        return payment;
    }
    
    private Date parseDate(String dateString) throws ParseException
    {   
        return dateFormat.parse(dateString);
    }
    
    private Map<String, String> getPaymentParams(Map<String, String[]> params, String key)
    {
        Map<String, String> paymentParams = new HashMap<String, String>();

        for (Entry<String, String[]> entry : params.entrySet()) {
            String name = entry.getKey();

            if (name.endsWith("["+key+"]")) {
                String paramKey = name.substring(0, name.indexOf('['));
                
                paymentParams.put(paramKey, entry.getValue()[0]);
            }
        }
        
        return paymentParams;
    }
    
    private String getPaymentKey(Map<String, String[]> params)
    {
        for (Entry<String, String[]> entry : params.entrySet()) {
            String name = entry.getKey();
            
            if (name.startsWith("send[")) {
                String key = name.substring(name.indexOf('[') + 1, name.indexOf(']'));
                
                return key;
            }
        }
        
        return null;
    }
    
    private Map<Long, Subject> subjectsToMap(List<Subject> subjects) {
        Map<Long, Subject> subjectsMap = new HashMap<Long, Subject>();
        
        for (Subject subject : subjects) {
            subjectsMap.put(subject.getId(), subject);
        }
        
        return subjectsMap;
    }
    
    private boolean isAjaxRequest(HttpServletRequest request) {
        return request.getHeader("X-Requested-With") != null && request.getHeader("X-Requested-With").equals("XMLHttpRequest");
    }
    
    private void manageRedirect(HttpServletRequest request, HttpServletResponse response, Payment payment) throws IOException
    {
        response.setCharacterEncoding("utf-8");
        
        if (isAjaxRequest(request)) {
            JsonSerializer<GregorianCalendar> serializer = new JsonSerializer<GregorianCalendar>() {
                @Override
                public JsonElement serialize(GregorianCalendar t, Type type, JsonSerializationContext jsc) {
                    return t == null ? null : new JsonPrimitive(dateFormat.format(t.getTime()));
                }
            };

            GsonBuilder builder = new GsonBuilder();

            builder.setDateFormat("yyyy-mm-dd");
            builder.registerTypeAdapter(GregorianCalendar.class, serializer);

            Gson gson = builder.create();
            
            response.getWriter().print(gson.toJson(new JsonResponse(notifications, payment)));
        }
        else {
            response.sendRedirect("list");
        }
    }
    
    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /**
     * Handles the HTTP
     * <code>GET</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Handles the HTTP
     * <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Returns a short description of the servlet.
     *
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Servlet for managing payments";
    }// </editor-fold>

    private void prepareListData(HttpServletRequest request) {
        PaymentManager paymentManager = (PaymentManager) getServletContext().getAttribute("paymentManager");
        SubjectManager subjectManager = (SubjectManager) getServletContext().getAttribute("subjectManager");
        
        List<Payment> payments = paymentManager.findAllPayments(filterSubject, filterDateFrom, filterDateTo);
        List<Subject> subjects = subjectManager.findAllSubjects();
        
        Map<Long, Subject> subjectsMap = subjectsToMap(subjects);
        
        request.setAttribute("payments", payments);
        request.setAttribute("subjects", subjectsMap);
        
        BigDecimal income = getIncome(payments);
        BigDecimal expenses = getExpenses(payments);
        
        request.setAttribute("income", income);
        request.setAttribute("expenses", expenses);
        request.setAttribute("balance", income.add(expenses));
        request.setAttribute("filterSubject", filterSubject);
        request.setAttribute("filterDateFrom", filterDateFrom);
        request.setAttribute("filterDateTo", filterDateTo);
    }
}
